package tmt.usercenter.web.service;

import com.tmt.annotation.EnumDescriptor;
import com.tmt.annotation.Select2OptionItem;
import com.tmt.annotation.parser.LazyLoadParser;
import com.tmt.datatable.TableRequest;
import com.tmt.datatable.TableResponse;
import com.tmt.datatable.editor.EditorModel;
import com.tmt.datatable.editor.ext.select2.Select2OptionValueLoader;
import com.tmt.jpa.jpaservice.ITmtBaseRepo;
import com.tmt.datatable.ext.controller.ITmtBaseService;
import com.tmt.datatable.ext.controller.PropertyLoader;
import com.tmt.datatable.ext.controller.SelectOptions;
import com.tmt.datatable.ext.controller.impl.PropertyLoaderImpl;
import com.tmt.datatable.helper.QueryConditionParser;
import com.tmt.datatable.helper.TableRequestHelper;
import com.tmt.datatable.model.FieldOption;
import com.tmt.helper.ReflectHelper;
import com.tmt.helper.StringHelper;
import com.tmt.jpa.jpaservice.ITmtJpaService;
import com.tmt.jpa.jpaservice.impl.DefaultTmtJpaService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import tmt.usercenter.web.domain.BaseDomain;
import tmt.usercenter.web.domain.TmtUser;
import tmt.usercenter.web.domain.security.UserDetailsImpl;

import javax.persistence.JoinColumn;
import javax.persistence.PersistenceUnitUtil;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
 * 基础服务
 * Created by Stark on 2017/3/2.
 */
public class TmtBaseService<T extends BaseDomain, ID extends Serializable>
        extends DefaultTmtJpaService<T,ID> implements ITmtBaseService<T, ID> {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    private ITmtBaseRepo<T, ID> mainRepo;

    public TmtBaseService(ITmtBaseRepo<T, ID> mainRepo, List<ITmtJpaService> jpaServices) {
        this.mainRepo = mainRepo;
        this.setMainJpaRepository(mainRepo);
        this.setOtherJpaService(jpaServices);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void executeTableAction(EditorModel<T> editorModel, String[] lazyProperties) {
        String action = editorModel.getAction();
        if (EditorModel.ACTION_UPLOAD.equals(action))
            return;

        try {
            PropertyLoader loader = new PropertyLoaderImpl();
            for (T entity : editorModel.getData()) {

                switch (action) {
                    case EditorModel.ACTION_CREATE:
                        initialCreator(entity);
                        create(entity);
                        break;
                    case EditorModel.ACTION_EDIT:
                        initialCreator(entity);
                        update(entity);
                        break;
                    case EditorModel.ACTION_REMOVE:
                        delete(entity);
                        editorModel.setError(null);
                        if (editorModel.getFieldErrors() != null)
                            editorModel.getFieldErrors().clear();
                        break;
                }
                PersistenceUnitUtil unitUtil = getRepo().getEntityManager().getEntityManagerFactory().getPersistenceUnitUtil();
                loader.loadLazyProperties(entity, lazyProperties, unitUtil);
            }
        } catch (Exception ex) {
            editorModel.setError(MessageFormat.format("操作执行失败：{0}", ex.getMessage()));
        }
    }

    @Override
    @Transactional(readOnly = true)
    public ITmtBaseRepo<T, ID> getRepo() {
        return mainRepo;
    }

    @Override
    @Transactional(readOnly = true)
    public Future<TableResponse<T>> queryByRequest(TableRequest request, Class<T> clazz, QueryConditionParser parser,
                                                   String[] lazyProperties,
                                                   Map<String, String[]> searchConditions,
                                                   Map<String, String[]> filterConditions,
                                                   Map<String, String[]> limitedConditions) throws ExecutionException, InterruptedException {
        TableRequestHelper helper = new TableRequestHelper();
        TableResponse<T> response = helper.queryByRequest(request, clazz, searchConditions, filterConditions, limitedConditions, parser, getRepo());
        loadLazyProperties(response.getData(), lazyProperties);
        return new AsyncResult<>(response);
    }

    @Override
    @Transactional(readOnly = true)
    public Future<SelectOptions> findForSelect2(Class<T> clazz, String idProp, String propName,
                                                boolean displayExtendInfo,
                                                String search, String tag, Pageable pageable,
                                                Map<String, String[]> filterConditions,
                                                Map<String, String[]> limitedConditions,
                                                Map<String, String[]> originParams) {

        SelectOptions options = new SelectOptions();

        if (!StringUtils.isEmpty(propName)) {
            Class<?> targetClazz = ReflectHelper.getActualClassByFieldName(clazz, propName);
            SelectOptions tmpOptions = null;
            if (targetClazz.isEnum()) {
                tmpOptions = findSelect2OptionForEnum(targetClazz, propName, search, filterConditions);
            } else if (targetClazz == boolean.class || targetClazz == Boolean.class) {
                tmpOptions = findSelect2OptionForBoolean(clazz, propName, search, filterConditions);
            }

            if (tmpOptions != null) {
                options.setTotalCount(tmpOptions.getTotalCount());
                options.setItems(tmpOptions.getItems());
            }
            return new AsyncResult<>(options);
        }

        List<Field> fieldList = ReflectHelper.findAllFieldByAnnotation(clazz, Select2OptionItem.class, -1);
        if (fieldList.size() <= 0)
            return new AsyncResult<>(options);

        Specification<T> s = getSpecificationBy(clazz, search, tag, filterConditions, limitedConditions);
        if (s == null)
            return new AsyncResult<>(options);

        return findForSelect2Ex(s, idProp, displayExtendInfo, filterConditions, originParams, pageable);
    }

    @Override
    @Transactional(readOnly = true)
    public Future<SelectOptions> findForSelect2Ex(Specification<T> specification, String idProp,
                                                  boolean displayExtendInfo,
                                                  Map<String, String[]> filterConditions, Map<String, String[]> originParams, Pageable pageable) {
        SelectOptions options = new SelectOptions();

        Page<T> page = getRepo().findAll(specification, pageable);
        options.setTotalCount(page.getTotalElements());

        List<Object> objList = new ArrayList<>();
        for (T t : page) {
            objList.add(t);
        }

        List<FieldOption> fieldOptions = Select2OptionValueLoader.getFieldOptionItems(objList, idProp, displayExtendInfo, null, filterConditions);
        fill(options, fieldOptions, null);
        return new AsyncResult<>(options);
    }

    @Override
    @Transactional(readOnly = true)
    public void clearProperties(Class<T> clazz, TableResponse<T> response) {
    }

    @Transactional(readOnly = true)
    public <X> void loadLazyProperties(List<X> entityList, String[] lazyProperties) {
        if (entityList == null || entityList.size() <= 0
                || lazyProperties == null || lazyProperties.length <= 0)
            return;

        PropertyLoader loader = new PropertyLoaderImpl();
        PersistenceUnitUtil unitUtil = getRepo().getEntityManager().getEntityManagerFactory().getPersistenceUnitUtil();
        entityList.forEach(x -> {
            try {
                loader.loadLazyProperties(x, lazyProperties, unitUtil);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    @Transactional(readOnly = true)
    public Long getCurrentUserId() {
        try {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            UserDetailsImpl details = (UserDetailsImpl) authentication.getPrincipal();
            return details.getUserIdInClient();
        } catch (Exception ex) {
        }
        return null;
    }

    /******************************************以下为保护方法******************************************/


    protected List<Predicate> getPredicateListByFilterCondition(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb,
                                                                Class<T> clazz,
                                                                Map<String, String[]> filterConditions,
                                                                Map<String, String[]> limitedConditions) {
        Map<String, String[]> filters = new HashMap<>();

        //处理前端代码附加的过滤条件
        filters.putAll(filterConditions);

        //limitedConditions为服务器代码附加的过滤条件，条件级别最高，所以用limitedConditions的项替换filters中的现有项。
        if (limitedConditions != null) {
            limitedConditions.keySet().stream().forEach(x -> {
                filters.remove(x);
                filters.put(x, limitedConditions.get(x));
            });
        }

        //处理前端代码附加的过滤条件,条件之间为与的关系
        List<Predicate> filterConditionList = new ArrayList<>();
        filters.entrySet().forEach(x ->
                TableRequestHelper.defaultTableConditionHandle(root, query, cb, clazz, x, filterConditionList));

        return filterConditionList;
    }

    protected List<Predicate> getPredicateListBySearchCondition(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb,
                                                                Class<T> clazz, String search) {
        List<Field> fieldList = ReflectHelper.findAllDeclaredFields(clazz);
        fieldList = fieldList.stream().filter(x -> x.getAnnotation(Select2OptionItem.class) != null)
                .collect(Collectors.toList());

        List<String> propNames = new ArrayList<>();
        fieldList.forEach(x -> {
            Select2OptionItem annotation = x.getAnnotation(Select2OptionItem.class);
            String path = x.getName();
            if (!StringUtils.isEmpty(annotation.propName())) {
                path += "." + annotation.propName();
            }
            propNames.add(path);
        });

        //构造搜索过滤条件,条件之间为“或/OR”关系。
        List<Predicate> searchConditionList = new ArrayList<>();
        if (!StringUtils.isEmpty(search)) {
            Map<String, String[]> searchFilter = new HashMap<>();
            TableRequestHelper.appendSearchConditions(clazz, propNames, search, searchFilter);
            searchFilter.entrySet().forEach(x ->
                    TableRequestHelper.defaultTableConditionHandle(root, query, cb, clazz, x, searchConditionList));
        }
        return searchConditionList;
    }

    protected Specification<T> getSpecificationBy(Class<T> clazz, String search, String tag,
                                                  Map<String, String[]> filterConditions,
                                                  Map<String, String[]> limitedConditions) {

        Specification<T> s = (root, query, cb) -> {

            List<Predicate> filterConditionList = getPredicateListByFilterCondition(root, query, cb, clazz, filterConditions, limitedConditions);
            List<Predicate> searchConditionList = getPredicateListBySearchCondition(root, query, cb, clazz, search);

            //filterConditionList中的条件为“与/AND”关系。
            Predicate condition1 = filterConditionList.isEmpty() ? cb.conjunction()
                    : cb.and(filterConditionList.toArray(new Predicate[filterConditionList.size()]));

            //searchConditionList中的条件为“或/OR”关系。
            Predicate condition2 = searchConditionList.isEmpty() ? cb.conjunction()
                    : cb.or(searchConditionList.toArray(new Predicate[searchConditionList.size()]));

            Predicate predicate = cb.and(condition1, condition2);

            return query.where(predicate).getRestriction();
        };
        return s;
    }


    /******************************************以下为私有方法******************************************/

    private void initialCreator(Object entity) {
        try {
            Field field = ReflectHelper.findFieldByName(entity.getClass(), "creator");
            if (field != null && field.getType() == TmtUser.class && field.getAnnotation(JoinColumn.class) != null) {
                Long userId = getCurrentUserId();
                Object value = ReflectHelper.getFieldValue(entity, "creator");
                if (value != null || userId == null)
                    return;

                TmtUser user = new TmtUser();
                user.setId(userId);
                ReflectHelper.setFieldValue(entity, "creator", user);
            }
        } catch (Exception ex) {
        }
    }

    private Class<T> getMainEntityClass() {
        Type type = this.getClass().getGenericSuperclass();
        return (Class<T>) ((ParameterizedType) type).getActualTypeArguments()[0];
    }

    private SelectOptions findSelect2OptionForEnum(Class<?> clazz, String propName,
                                                   String search,
                                                   Map<String, String[]> filterConditions) {
        SelectOptions options = new SelectOptions();

        List<FieldOption> fieldOptions = new ArrayList<>();
        List<Field> fields = ReflectHelper.findAllDeclaredFields(clazz);
        fields.forEach(t -> {
            if (!StringUtils.isEmpty(search) && !t.getName().startsWith(search))
                return;

            EnumDescriptor annotation = t.getAnnotation(EnumDescriptor.class);
            if (annotation == null)
                return;

            boolean match = true;
            for (String key : filterConditions.keySet()) {
                String[] values = filterConditions.get(key);
                switch (key) {
                    case "groupId":
                        match = !StringUtils.isEmpty(annotation.groupId())
                                && (annotation.groupId().contains(String.format(" %s ", values[0].trim()))
                                || Objects.equals(annotation.groupId(), values[0].trim()));
                        break;
                    case "title":
                        match = Objects.equals(annotation.title(), values[0]);
                        break;
                    case "description":
                        match = Objects.equals(annotation.description(), values[0]);
                        break;
                    default:
                        continue;
                }
                if (!match) break;
            }

            if (!match && annotation.hideWhenGroupIdNotMatch())
                return;

            HashMap<String, Object> map = new HashMap<String, Object>() {{
                put("title", annotation.description());
            }};

            FieldOption<String> fieldOption = new FieldOption<>();
            fieldOption.setLabel(annotation.title());
            fieldOption.setValue(t.getName());
            fieldOption.setDisabled(!match);
            fieldOption.setAttr(map);
            fieldOptions.add(fieldOption);

        });
        fill(options, fieldOptions, filterConditions);
        options.setTotalCount(fieldOptions.size());
        return options;
    }

    private SelectOptions findSelect2OptionForBoolean(Class<?> clazz, String propName,
                                                      String search,
                                                      Map<String, String[]> filterConditions) {
        SelectOptions options = new SelectOptions();

        List<FieldOption> fieldOptions = new ArrayList<>();

        Field field = ReflectHelper.findFieldByName(clazz, propName);
        Select2OptionItem annotation = field.getAnnotation(Select2OptionItem.class);
        if (annotation.display()) {
            for (int i = 0; i < annotation.booleanConverter().length; i++) {
                FieldOption<String> fieldOption = new FieldOption<>();
                fieldOption.setLabel(annotation.booleanConverter()[i]);
                fieldOption.setValue(i == 0 ? "true" : "false");
                fieldOptions.add(fieldOption);
            }
            fill(options, fieldOptions, filterConditions);
            options.setTotalCount(fieldOptions.size());
        }
        return options;
    }

    private void fill(SelectOptions options, List<FieldOption> fieldOptions, Map<String, String[]> filterConditions) {
        fieldOptions.forEach(x -> {
            boolean control = true;
            try {
                if (filterConditions != null && filterConditions.containsKey(x.getDisableControl())) {
                    String[] val = filterConditions.get(x.getDisableControl());
                    control = StringHelper.convertTo(val[0], boolean.class);
                }
            } catch (Exception ex) {
            }

            Map<String, Object> map1 = new HashMap<>();
            map1.put("id", x.getId());
            map1.put("text", x.getText());
            map1.put("disabled", (control && x.isDisabled()));
            map1.put("attr", x.getAttr());
            options.addItem(map1);
        });
    }


	/*protected void initialMainRepository(){
		Class<T> targetClazz = getMainEntityClass();

		Field[] fields = this.getClass().getDeclaredFields();
		for (Field field : fields) {
			try {
				Type[] superInterfaces = field.getType().getGenericInterfaces();
				Optional optional = Arrays.stream(superInterfaces).filter(x->{
					try {
						ParameterizedType paramType = (ParameterizedType) x;
						return ITmtBaseRepo.class.isAssignableFrom((Class)paramType.getRawType()) && paramType.getActualTypeArguments()[0] == targetClazz;
					}catch (Exception ex){}
					return false;
				}).findFirst();

				if (optional.isPresent()){
					try {
						//作为public字段直接读取值。
						mainRepo = (ITmtBaseRepo<T, ID>) field.get(this);
					}catch (Exception ex){}

					//如果不是public字段，则通过get方法获取值
					if (mainRepo == null){
						mainRepo = (ITmtBaseRepo<T,ID>)ReflectHelper.getFieldValue(this, field.getName());
					}
					break;
				}

			}catch (Exception ex){}
		}

		if (mainRepo != null)
			setMainJpaRepository(mainRepo);
	}

	protected void initialOtherRepositories(){
		List<ITmtJpaService> resultList = new ArrayList<>();

		Field[] fields = this.getClass().getDeclaredFields();
		for (Field field : fields) {
			try {
				Type[] superInterfaces = field.getType().getGenericInterfaces();
				Optional optional = Arrays.stream(superInterfaces).filter(x->{
					try {
						ParameterizedType paramType = (ParameterizedType) x;
						return ITmtJpaService.class.isAssignableFrom((Class)paramType.getRawType());
					}catch (Exception ex){}
					return false;
				}).findFirst();

				ITmtJpaService result = null;
				if (optional.isPresent()){
					try {
						//作为public字段直接读取值。
						result = (ITmtJpaService) field.get(this);
					}catch (Exception ex){}

					//如果不是public字段，则通过get方法获取值
					if (result == null){
						result = (ITmtJpaService)ReflectHelper.getFieldValue(this, field.getName());
					}
				}

				if (result != null)
					resultList.add(result);

			}catch (Exception ex){}
		}

		setOtherJpaRepositories(resultList);
	}*/

}
